package org.lc.solon.cmd.impl;

import lombok.Getter;
import org.jline.builtins.ConfigurationPath;
import org.jline.console.CommandMethods;
import org.jline.console.CommandRegistry;
import org.jline.console.SystemRegistry;
import org.jline.console.impl.SystemRegistryImpl;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.Parser;
import org.jline.reader.impl.DefaultHighlighter;
import org.jline.reader.impl.DefaultParser;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.lc.solon.cmd.exception.CmdException;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;

import static org.lc.solon.cmd.ResultManager.handleResult;


@Getter
public class TerminalRunner implements Closeable {
    @Getter
    private static final Path workDir = Paths.get(System.getProperty("user.dir"));

    @Getter
    private static volatile TerminalRunner instance = null;

    Terminal terminal;

    LineReader reader;

    String prompt;

    ConfigurationPath configurationPath = new ConfigurationPath(workDir, workDir);

    SystemRegistry registry;


    public TerminalRunner(Terminal terminal, Parser parser, LineReader reader, String prompt,
                          CommandRegistry commandRegistry,
                          SystemRegistry registry) throws IOException {
        if (instance != null) {
            throw new UnsupportedOperationException("TerminalRunner只能实例化一次");
        }
        synchronized (TerminalRunner.class) {
            if (instance != null) {
                throw new UnsupportedOperationException("TerminalRunner只能实例化一次");
            }
            instance = this;
        }
        if (terminal == null) {
            terminal = TerminalBuilder.terminal();
        }
        if (parser == null) {
            parser = new DefaultParser();
        }
        this.terminal = terminal;
        if (registry == null) {
            registry = new SystemRegistryImpl(parser, terminal, TerminalRunner::getWorkDir, configurationPath);
            removeJLineCommand(registry);
        }
        if (commandRegistry == null) {
            commandRegistry = DefaultSolonCommandRegistry.getInstance();
        }
        registry.setCommandRegistries(commandRegistry);
        this.registry = registry;
        if (reader == null) {
            reader = LineReaderBuilder.builder()
                    .terminal(terminal)
                    .completer(registry.completer())
                    .highlighter(new DefaultHighlighter())
                    .build();
        }
        this.reader = reader;
        if (prompt == null) {
            prompt = "cmd > ";
        }
        this.prompt = prompt;
    }

    /**
     * 启动终端命令行监听执行
     * 只能在主线程运行，否则readLine的结果会不正确
     */
    @SuppressWarnings("InfiniteLoopStatement")
    public void run() {
        while (true) {
            String line = null;
            try {
                line = reader.readLine(prompt);
                Object result = registry.execute(line);
                handleResult(result, line);
            } catch (Throwable e) {
                handleResult(e, line);
            }
        }
    }

    @Override
    public void close() throws IOException {
        if (terminal != null) {
            terminal.close();
        }
    }

    private void removeJLineCommand(SystemRegistry registry) {
        try {
            Field field = SystemRegistryImpl.class.getDeclaredField("commandExecute");
            field.setAccessible(true);
            @SuppressWarnings("unchecked")
            Map<String, CommandMethods> map = (Map<String, CommandMethods>) field.get(registry);
            map.clear();
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new CmdException("去除jline内置命令失败", e);
        }
    }
}
